#ifndef _SASLAUTHENTICATION_H_
#define _SASLAUTHENTICATION_H_

#include <map>
#include <list>

#include <UserAuthentication.h>
//#include <sasl/SASLGSSAPIMechanism.h>
//#include <sasl/SASLPlainMechanism.h>
//#include <sasl/SASLMechanism.h>

class SASLGSSAPIMechanism;
class SASLPlainMechanism;
class SASLMechanism;

typedef SASLMechanism* (*MechanismCreateFunc)(SASLAuthentication * s);

template<typename T>
class MechanismCreator
{
public:
    static SASLMechanism* Create(SASLAuthentication * saslAuthentication)
    {
		return new T(saslAuthentication);
    }
};

//MechanismCreateFunc gssapiMechanismFunc = MechanismCreator<SASLGSSAPIMechanism *>::Create;
//MechanismCreateFunc plainMechanismFunc = MechanismCreator<SASLPlainMechanism *>::Create;


class ImplementedMechanisms : public std::map<std::string, MechanismCreateFunc> {
public:
	void registerSASLMechanism(const std::string& name, MechanismCreateFunc mCreator);
	void getSASLMechanism(const std::string& name);
	void setSASLAuthentication(SASLAuthentication * s) { saslAuthentication = s;}
private:
	SASLAuthentication * saslAuthentication;
};


/**
 * This class is responsible authenticating the user using SASL, binding the resource
 * to the connection and establishing a session with the server.<p>
 *
 * Once TLS has been negotiated (i.e. the connection has been secured) it is possible to
 * register with the server, authenticate using Non-SASL or authenticate using SASL. If the
 * server supports SASL then Smack will first try to authenticate using SASL. But if that
 * fails then Non-SASL will be tried.<p>
 *
 * The server may support many SASL mechanisms to use for authenticating. Out of the box
 * Smack provides SASL PLAIN but it is possible to register new SASL Mechanisms. Use
 * {@link #registerSASLMechanism(int, String, Class)} to add new mechanisms. See
 * {@link SASLMechanism}.<p>
 *
 * Once the user has been authenticated with SASL, it is necessary to bind a resource for
 * the connection. If no resource is passed in {@link #authenticate(String, String, String)}
 * then the server will assign a resource for the connection. In case a resource is passed
 * then the server will receive the desired resource but may assign a modified resource for
 * the connection.<p>
 *
 * Once a resource has been binded and if the server supports sessions then Smack will establish
 * a session so that instant messaging and presence functionalities may be used.
 *
 * @author Gaston Dombiak
 */
class SASLAuthentication : public UserAuthentication {
public:
	SASLAuthentication(XMPPConnection * connection);

    /**
     * Registers a new SASL mechanism in the specified preference position. The client will try
     * to authenticate using the most prefered SASL mechanism that is also supported by the server.
     * <p/>
     * <p/>
     * Use the <tt>index</tt> parameter to set the level of preference of the new SASL mechanism.
     * A value of 0 means that the mechanism is the most prefered one.
     *
     * @param index  preference position amongst all the implemented SASL mechanism. Starts with 0.
     * @param name   common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
     * @param mClass a SASLMechanism subclass.
     */
    static void registerSASLMechanism(int index, std::string & name, MechanismCreateFunc func) ;
	
    /**
     * Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't
     * be possible to authenticate users using the removed SASL mechanism.
     *
     * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
     */
    static void unregisterSASLMechanism(std::string & name);

    /**
     * Returns the registerd SASLMechanism classes sorted by the level of preference.
     *
     * @return the registerd SASLMechanism classes sorted by the level of preference.
     */
    static std::list<SASLMechanism*> getRegisterSASLMechanisms() ;

    /**
     * Returns true if the server offered ANONYMOUS SASL as a way to authenticate users.
     *
     * @return true if the server offered ANONYMOUS SASL as a way to authenticate users.
     */
    bool hasAnonymousAuthentication() {
        //return serverMechanisms.contains("ANONYMOUS");
		return true;
    }	
	
    /**
     * Returns true if the server offered SASL authentication besides ANONYMOUS SASL.
     *
     * @return true if the server offered SASL authentication besides ANONYMOUS SASL.
     */
    bool hasNonAnonymousAuthentication();
	
    /**
     * Performs SASL authentication of the specified user. If SASL authentication was successful
     * then resource binding and session establishment will be performed. This method will return
     * the full JID provided by the server while binding a resource to the connection.<p>
     *
     * The server may assign a full JID with a username or resource different than the requested
     * by this method.
     *
     * @param username the username that is authenticating with the server.
     * @param password the password to send to the server.
     * @param resource the desired resource.
     * @return the full JID provided by the server while binding a resource to the connection.
     * @throws XMPPException if an error occures while authenticating.
     */
	std::string authenticate(std::string & username, std::string & password, std::string & resource);

    /**
     * Performs ANONYMOUS SASL authentication. If SASL authentication was successful
     * then resource binding and session establishment will be performed. This method will return
     * the full JID provided by the server while binding a resource to the connection.<p>
     *
     * The server will assign a full JID with a randomly generated resource and possibly with
     * no username.
     *
     * @return the full JID provided by the server while binding a resource to the connection.
     * @throws XMPPException if an error occures while authenticating.
     */
	std::string authenticateAnonymously();

    /**
     * Sets the available SASL mechanism reported by the server. The server will report the
     * available SASL mechanism once the TLS negotiation was successful. This information is
     * stored and will be used when doing the authentication for logging in the user.
     *
     * @param mechanisms collection of strings with the available SASL mechanism reported
     *                   by the server.
     */
    void setAvailableSASLMethods(std::list<std::string> & mechanisms) {
        serverMechanisms = mechanisms;
    }	
	
    /**
     * Returns true if the user was able to authenticate with the server usins SASL.
     *
     * @return true if the user was able to authenticate with the server usins SASL.
     */
    bool isAuthenticated() {
        return saslNegotiated;
    }

    /**
     * The server is challenging the SASL authentication we just sent. Forward the challenge
     * to the current SASLMechanism we are using. The SASLMechanism will send a response to
     * the server. The length of the challenge-response sequence varies according to the
     * SASLMechanism in use.
     *
     * @param challenge a base64 encoded string representing the challenge.
     * @throws IOException If a network error occures while authenticating.
     */
    void challengeReceived(std::string & challenge) ;

    /**
     * Notification message saying that SASL authentication was successful. The next step
     * would be to bind the resource.
     */
    void authenticated(); 
	    
	/**
     * Notification message saying that SASL authentication has failed. The server may have
     * closed the connection depending on the number of possible retries.
     */
    void authenticationFailed() ;
	
    /**
     * Notification message saying that the server requires the client to bind a
     * resource to the stream.
     */
    void bindingRequired();
	
	void send(std::string & stanza); 

    /**
     * Notification message saying that the server supports sessions. When a server supports
     * sessions the client needs to send a Session packet after successfully binding a resource
     * for the session.
     */
    void sessionsSupported() {
        sessionSupported = true;
    }
protected: 
    /**
     * Initializes the internal state in order to be able to be reused. The authentication
     * is used by the connection at the first login and then reused after the connection
     * is disconnected and then reconnected.
     */
    void init();
private:
	std::string bindResourceAndEstablishSession(std::string & resource);

	static ImplementedMechanisms implementedMechanisms;
    static std::list<std::string> mechanismsPreferences;

    XMPPConnection * connection;
	std::list<std::string> serverMechanisms ;
    SASLMechanism * currentMechanism;

    /**
     * Boolean indicating if SASL negotiation has finished and was successful.
     */
    bool saslNegotiated;

    /**
     * Boolean indication if SASL authentication has failed. When failed the server may end
     * the connection.
     */
    bool saslFailed;
    bool resourceBinded;
    bool sessionSupported;

};

#endif // -- _SASLAUTHENTICATION_H_
